////////////////////////////////////////////////////////////////////////////////
//
//  Copyright (C) 2003-2006 Adobe Macromedia Software LLC and its licensors.
//  All Rights Reserved. The following is Source Code and is subject to all
//  restrictions on such code as contained in the End User License Agreement
//  accompanying this product.
//
////////////////////////////////////////////////////////////////////////////////

package mx.charts.series
{

import flash.display.DisplayObject;
import flash.display.Graphics;
import flash.filters.DropShadowFilter;
import flash.geom.Rectangle;
import mx.charts.HitData;
import mx.charts.chartClasses.DataDescription;
import mx.charts.chartClasses.IBar;
import mx.charts.chartClasses.IStackable;
import mx.charts.chartClasses.InstanceCache;
import mx.charts.chartClasses.LegendData;
import mx.charts.chartClasses.Series;
import mx.charts.chartClasses.StackedSeries;
import mx.charts.renderers.BoxItemRenderer;
import mx.charts.series.items.BarSeriesItem;
import mx.charts.series.renderData.BarSeriesRenderData;
import mx.charts.styles.HaloDefaults;
import mx.collections.CursorBookmark;
import mx.collections.ICollectionView;
import mx.collections.IViewCursor;
import mx.core.ClassFactory;
import mx.core.IDataRenderer;
import mx.core.IFactory;
import mx.core.IFlexDisplayObject;
import mx.core.mx_internal;
import mx.graphics.IFill;
import mx.graphics.IStroke;
import mx.graphics.SolidColor;
import mx.styles.CSSStyleDeclaration;
import mx.styles.ISimpleStyleClient;
import mx.charts.chartClasses.CartesianTransform;
import flash.utils.Dictionary;

use namespace mx_internal;

include "../styles/metadata/FillStrokeStyles.as"
include "../styles/metadata/ItemRendererStyles.as"

/**
 *  Defines a data series for a BarChart control.
 *  By default, this class uses the BoxItemRenderer class. 
 *  Optionally, you can define an itemRenderer for the data series.
 *  The itemRenderer must implement the IDataRenderer interface. 
 *
 *  @mxml
 *  
 *  <p>The <code>&lt;mx:BarSeries&gt;</code> tag inherits all the properties
 *  of its parent classes and adds the following properties:</p>
 *  
 *  <pre>
 *  &lt;mx:BarSeries
 *    <strong>Properties</strong>
 *    barWidthRatio=".65"
 *    maxBarWidth="<i>No default</i>"
 *    minField="null"
 *    offset="<i>No default</i>"
 *    stacker="<i>No default</i>"
 *    stackTotals="<i>No default</i>" 
 *    xField="null"
 *    yField="null"
 *  
 *    <strong>Styles</strong>
 *    fill="<i>IFill; no default</i>"
 *    itemRenderer="<i>itemRenderer</i>"
 *    legendMarkerRenderer="<i>Defaults to series's itemRenderer</i>"
 *    stroke="<i>IStroke; no default</i>"
 *  /&gt;
 *  </pre>
 *  
 *  @see mx.charts.BarChart
 *  
 *  @includeExample ../examples/Column_BarChartExample.mxml
 *  
 */
public class BarSeries extends Series implements IStackable, IBar
{
    include "../../core/Version.as";

	//--------------------------------------------------------------------------
	//
	//  Class initialization
	//
	//--------------------------------------------------------------------------

	/**
	 *  @private
	 */
	private static var stylesInited:Boolean = initStyles();
		
	/**
	 *  @private
	 */
	private static function initStyles():Boolean
	{
		HaloDefaults.init();
		
		var barSeriesStyle:CSSStyleDeclaration =
			HaloDefaults.createSelector("BarSeries");		

		barSeriesStyle.defaultFactory = function():void
		{
			this.fill = new SolidColor(0);
			this.itemRenderer = new ClassFactory(BoxItemRenderer);
			this.stroke = HaloDefaults.emptyStroke;
		}

		return true;
	}
	
	//--------------------------------------------------------------------------
	//
	//  Constructor
	//
	//--------------------------------------------------------------------------
	
	/**
	 *  Constructor.
	 */	
	public function BarSeries()
	{
		super();

		_instanceCache = new InstanceCache(null,this);
		_instanceCache.creationCallback = applyItemRendererProperties;

	}
	
	//--------------------------------------------------------------------------
	//
	//  Variables
	//
	//--------------------------------------------------------------------------

	/**
	 *  @private
	 */
	private var _instanceCache:InstanceCache;

	/**
	 *  @private
	 */
	private var _renderData:BarSeriesRenderData;


	//--------------------------------------------------------------------------
	//
	//  Overridden properties:Series
	//
	//--------------------------------------------------------------------------

	//----------------------------------
	//  legendData
	//----------------------------------

	/**
	 *  @private
	 */
	override public function get legendData():Array
	{
		var marker:IFlexDisplayObject;

		var ld:LegendData = new LegendData();
		ld.element = this;
		
		var markerFactory:IFactory = getStyle("legendMarkerRenderer");
		if (markerFactory == null)
			markerFactory = getStyle("itemRenderer");
		if (markerFactory) 
		{
			marker = markerFactory.newInstance();
			if (marker as ISimpleStyleClient)
				(marker as ISimpleStyleClient).styleName = this;
		}
		ld.marker = marker;
		ld.label = displayName;	

		return [ld];
	}

	//----------------------------------
	//  renderData
	//----------------------------------

	/**
	 *  @private
	 */
	override protected function get renderData():Object
	{
		if (_renderData == null)
		{
			var renderDataType:Class = this.renderDataType;
			var rv:BarSeriesRenderData = new renderDataType();
			rv.cache = rv.filteredCache = [];
			rv.renderedHalfWidth = 0;
			rv.renderedYOffset = 0;
			rv.renderedBase = 0;

			return rv;
		}

		return _renderData;
	}

	//--------------------------------------------------------------------------
	//
	//  Properties
	//
	//--------------------------------------------------------------------------

	//----------------------------------
	//  barWidthRatio
	//----------------------------------
	
	/**
	 *  @private
	 *  Storage for the barWidthRatio property.
	 */
	private var _barWidthRatio:Number = 0.65;

    [Inspectable(category="General", defaultValue="0.65")]

	/**
	 *  Specifies how wide to render the bars relative to the category width.
	 *  A value of 1 uses the entire space, while a value of .6 
	 *  uses 60% of the bar's available space. 
	 *  You typically do not set this property directly.
	 *  The actual bar width used is the smaller of <code>barWidthRatio</code> and the <code>maxbarWidth</code> property
 	 *  
 	 *  @default .65
	 */
	public function get barWidthRatio():Number
	{
		return _barWidthRatio;
	}

	/**
	 *  @private
	 */
	public function set barWidthRatio(value:Number):void
	{
		_barWidthRatio = value;

		invalidateTransform();
	}

	//----------------------------------
	//  items
	//----------------------------------

	/**
	 *  @private
	 */
	mx_internal function get items():Array
	{
		return _renderData ? _renderData.filteredCache : null;
	}

	//----------------------------------
	//  itemType
	//----------------------------------

	[Inspectable(environment="none")]

	/**
	 *  The subtype of ChartItem used by this series to represent individual items.
	 *  Subclasses can override and return a more specialized class if they need to store additional information in the items.
	 */
	protected function get itemType():Class
	{
		return BarSeriesItem;
	}

	//----------------------------------
	//  maxBarWidth
	//----------------------------------

	/**
	 *  @private
	 *  Storage for the maxBarWidth property.
	 */
	private var _maxBarWidth:Number;

    [Inspectable(category="General")]

	/**
	 *  Specifies the width of the bars, in pixels.
	 *  The actual bar width used is the smaller of this style and the <code>barWidthRatio</code> property.
	 *  Clustered bars divide this space proportionally among the bars in each cluster. 
	 */
	public function get maxBarWidth():Number
	{
		return _maxBarWidth;
	}

	/**
	 *  @private
	 */
	public function set maxBarWidth(value:Number):void
	{
		_maxBarWidth = value;

		invalidateTransform();
	}
	
	//----------------------------------
	//  minField
	//----------------------------------

	/**
	 *  @private
	 *  Storage for the minField property.
	 */
	private var _minField:String = "";

	/**
	 *  @private
	 */
	private var _userMinField:String = "";

    [Inspectable(category="General")]

	/**
	 *  Specifies the field of the data provider that determines the bottom of each bar. 
	 *  If <code>null</code>, the columns are based at 
	 *  the range minimum (or maximum, if the field value is negative). 
	 *  
	 *  @default null
	 */
	public function get minField():String
	{
		return _minField;
	}

	/**
	 *  @private
	 */
	public function set minField(value:String):void
	{
		_userMinField = value;
		_minField = value;

		dataChanged();
	}

	//----------------------------------
	//  offset
	//----------------------------------

	/**
	 *  @private
	 *  Storage for the offset property.
	 */
	private var _offset:Number = 0;

    [Inspectable(category="General", defaultValue="0")]

	/**
	 *  Specifies how far to offset the center of the bars from the center of the available space, relative the category width. 
	 *  The range of values is a percentage in the range <code>-100</code> to <code>100</code>. 
	 *  Set to <code>0</code> to center the bars in the space. Set to <code>-50</code> to center the column at the beginning of the available space. You typically do not set this property directly.
	 *  
	 *  @default 0
	 */
	public function get offset():Number
	{
		return _offset;
	}

	/**
	 *  @private
	 */
	public function set offset(value:Number):void
	{
		_offset = value;

		invalidateTransform();
	}

	//----------------------------------
	//  renderDataType
	//----------------------------------

	[Inspectable(environment="none")]

	/**
	 *  The subtype of ChartRenderData used by this series to store all data necessary to render.
	 *  Subclasses can override and return a more specialized class if they need to store additional information for rendering.
	 */
	protected function get renderDataType():Class
	{
		return BarSeriesRenderData;
	}

	//----------------------------------
	//  stacker
	//----------------------------------

	/**
	 *  @private
	 */
	private var _stacker:StackedSeries;	

	/**
	 *  @private
	 */
	private var _stacked:Boolean = false;
	/**
	 *  The StackedSeries associated with this BarSeries.
	 *  The stacker manages the series's stacking behavior.
	 */
	public function get stacker():StackedSeries
	{
		return _stacker;
	}
	
	/**
	 *  @private
	 */
	public function set stacker(value:StackedSeries):void
	{
		_stacker = value;
	}

	//----------------------------------
	//  xField
	//----------------------------------

	/**
	 *  @private
	 *  Storage for the xField property.
	 */
	private var _xField:String = "";

    [Inspectable(category="General")]

	/**
	 *  Specifies the field of the data provider that determines the x-axis location of the top of each bar. If <code>null</code>, 
	 *  the BarSeries assumes that the data provider is an Array of numbers, and uses the numbers as values.
	 *  
	 *  @default null
	 */
	public function get xField():String
	{
		return _xField;
	}

	/**
	 *  @private
	 */
	public function set xField(value:String):void
	{
		_xField = value;

		dataChanged();
	}

	//----------------------------------
	//  yField
	//----------------------------------

	/**
	 *  @private
	 *  Storage for the yField property.
	 */
	private var _yField:String = "";

    [Inspectable(category="General")]

	/** 
	 *  Specifies the field of the data provider that determines the y-axis location of the bottom of each bar in the chart. 
	 *  If <code>null</code>, Flex arranges the bars in the order of the data in the data provider. 
	 *  
	 *  @default null
	 */
	public function get yField():String
	{
		return _yField;
	}

	/**
	 *  @private
	 */
	public function set yField(value:String):void
	{
		_yField = value;

		dataChanged();
	}

	//--------------------------------------------------------------------------
	//
	//  Overridden methods: UIComponent
	//
	//--------------------------------------------------------------------------
	
	/**
	 *  @private
	 */
	override protected function updateDisplayList(unscaledWidth:Number,
												  unscaledHeight:Number):void
	{
		super.updateDisplayList(unscaledWidth, unscaledHeight);
				
		if (!dataTransform)
			return;
		
		var g:Graphics = graphics;
		g.clear();

		if (!isNaN(_maxBarWidth) && (_maxBarWidth <= 0 || _barWidthRatio <= 0))
			return;
		
		var renderData:BarSeriesRenderData =
			transitionRenderData ?
			BarSeriesRenderData(transitionRenderData) :
			_renderData;

		var activeRenderCache:Array = renderData.filteredCache;

		var i:int;
		var sampleCount:int = activeRenderCache.length;

		var rc:Rectangle;
		var instances:Array;
		var inst:IFlexDisplayObject; 

		_instanceCache.factory = getStyle("itemRenderer");
		_instanceCache.count = sampleCount;			
		instances = _instanceCache.instances;

		var bSetData:Boolean = sampleCount > 0 &&
							   instances[0] is IDataRenderer;

		if (transitionRenderData != null &&
			transitionRenderData.elementBounds != null)
		{
			var elementBounds:Array = transitionRenderData.elementBounds;

			for (i = 0; i < sampleCount; i++)
			{
				inst = instances[i];
				v = activeRenderCache[i];
				v.itemRenderer = inst;
				if (bSetData)
					(inst as IDataRenderer).data = v;
				rc = elementBounds[i];
				inst.move(rc.left, rc.top);
				inst.setActualSize(rc.width, rc.height);
			}
		}
		else
		{
			var bo:Number = renderData.renderedHalfWidth +
							renderData.renderedYOffset;
			
			var to:Number = -renderData.renderedHalfWidth +
							renderData.renderedYOffset;

			rc = new Rectangle();
			for (i = 0; i < sampleCount; i++)
			{
				var v:BarSeriesItem = activeRenderCache[i];

				rc.top = v.y + to;
				rc.bottom = v.y + bo;
				rc.right = v.x;
				if (!isNaN(v.min))
					rc.left = v.min;
				else
					rc.left = renderData.renderedBase;

				inst = instances[i]
				v = activeRenderCache[i];
				v.itemRenderer = inst;
				if (bSetData)
					(inst as IDataRenderer).data = v;
				inst.move(rc.left,rc.top);
				inst.setActualSize(rc.width,rc.height);
			}
		}
	}

	/**
	 *  @private
	 */
	override public function styleChanged(styleProp:String):void
	{
		super.styleChanged(styleProp);
		
		var styles:String = "stroke fill";
		if (styleProp == null || styleProp == "" ||
			styles.indexOf(styleProp) != -1)
		{
			invalidateDisplayList();
			legendDataChanged();
		}
	}
	
	//--------------------------------------------------------------------------
	//
	//  Overridden methods: ChartElement
	//
	//--------------------------------------------------------------------------

	/**
	 *  @private
	 */
	override public function describeData(dimension:String, requiredFields:uint):Array
	{
		validateData();

		if (_renderData.cache.length == 0)
			return [];

		var description:DataDescription = new DataDescription();
		description.boundedValues = null;

		if (dimension == CartesianTransform.VERTICAL_AXIS)
		{
			if (_yField != "")
			{
				if ((requiredFields & DataDescription.REQUIRED_MIN_INTERVAL) != 0)
				{
					// if we need to know the min interval, then we rely on the cache being in order. So we need to sort it if it
					// hasn't already been sorted
					var sortedCache:Array = _renderData.cache.concat();
					sortedCache.sortOn("yNumber",Array.NUMERIC);		
					extractMinMax(sortedCache,"yNumber",description, (requiredFields & DataDescription.REQUIRED_MIN_INTERVAL) != 0);
				}
				else
				{
					extractMinMax(_renderData.cache,"yNumber",description, (requiredFields & DataDescription.REQUIRED_MIN_INTERVAL) != 0);
				}
			}
			else 
			{
				description.min = _renderData.cache[0].yNumber;
				description.max =
					_renderData.cache[_renderData.cache.length - 1].yNumber;
				if ((requiredFields & DataDescription.REQUIRED_MIN_INTERVAL) != 0)
				{
					extractMinInterval(_renderData.cache,"xNumber",description);
				}
			}
			description.padding = .5;
		}
		else if (dimension == CartesianTransform.HORIZONTAL_AXIS)
		{
			extractMinMax(_renderData.cache, "xNumber", description);
			
			if (_minField != "")
				extractMinMax(_renderData.cache, "minNumber", description);
		}
		else
		{
			return [];
		}
			
		return [ description ];
	}

	/**
	 *  @private
	 */
	override public function findDataPoints(x:Number, y:Number,
											sensitivity:Number):Array
	{
		if (interactive == false)
			return [];

		var modY:Number = y + _renderData.renderedYOffset;
		var minDist:Number = _renderData.renderedHalfWidth + sensitivity;
		var minItem:BarSeriesItem;		

		var len:int = _renderData.filteredCache.length;
		var bottom:Number = unscaledHeight;
		for (var i:int = 0; i < len; i++)
		{
			var v:BarSeriesItem = _renderData.filteredCache[i];
			
			if (v.y + _renderData.renderedYOffset +
				_renderData.renderedHalfWidth <= 0)
			{
				continue;
			}
			
			if (v.y + _renderData.renderedYOffset -
				_renderData.renderedHalfWidth >= bottom)
			{
				continue;
			}
				
			var dist:Number = Math.abs((v.y + _renderData.renderedYOffset) - y);
			if (dist > minDist)
				continue;
				
			var base:Number = (isNaN(v.min)) ? _renderData.renderedBase : v.min;
			
			var max:Number = Math.max(v.x, base);
			var min:Number = Math.min(v.x, base);

			if (min - x > sensitivity)
				continue;

			if (x - max > sensitivity)
				continue;
				
			minDist = dist;
			minItem = v;
			if (dist < _renderData.renderedHalfWidth)
			{
				// we're actually inside the column, so go no further.
				break;
			}
		}

		if (minItem != null)
		{
			var xpos:Number = isNaN(minItem.min) ?
							  minItem.x :
							  Math.max(minItem.x, minItem.min);
			
			var hd:HitData = new HitData(createDataID(minItem.index), 
										 Math.sqrt(minDist), xpos,
										 minItem.y + _renderData.renderedYOffset,
										 minItem);
			hd.dataTipFunction = formatDataTip;
			return [ hd ];
		}

		return [];
	}


	//--------------------------------------------------------------------------
	//
	//  Overridden methods: Series
	//
	//--------------------------------------------------------------------------

		
	/**
	 *  Stacks the series. Normally, a series implements the <code>updateData()</code> method
	 *  to load its data out of the data provider. But a stacking series performs special 
	 *  operations because its values are not necessarily stored in its data provider. 
	 *  Its values are whatever is stored in its data provider, summed with the values 
	 *  that are loaded by the object it stacks on top of.
	 *  <p>A custom stacking series should implement the <code>stack()</code> method by loading its 
	 *  data out of its data provider, adding it to the base values stored in the dictionary
	 *  to get the real values it should render with, and replacing the values in the dictionary 
	 *  with its new, summed values.</p>
	 *  
	 *  @param stackedYValueDictionary Contains the base values that the series should stack 
	 *  on top of. The keys in the dictionary are the x values, and the values are the y values.
	 *  
	 *  @param previousElement The previous element in the stack. If, for example, the element
	 *  is of the same type, you can use access to this property to avoid duplicate effort when
	 *  rendering.
	 *  
	 *  @return The maximum value in the newly stacked series.
	 */
	public function stack(stackedYValueDictionary:Dictionary, previousElement:IStackable):Number
	{
		var i:uint = 0;
		var itemClass:Class = itemType;
		var chartItem:BarSeriesItem;
		var haveYField:Boolean = (_yField != null && _yField != "");
		var haveXField:Boolean = (_xField != null && _xField != "");
		var maxValue:Number = 0;

		var renderDataType:Class = this.renderDataType;
		_renderData= new renderDataType();		
		_renderData.cache = [];

		if (cursor != null)
		{
			cursor.seek(CursorBookmark.FIRST);
			while (!cursor.afterLast)
			{
				var dataItem:* = cursor.current;
				_renderData.cache[i] = chartItem = new itemClass(this,dataItem,i);

				var xValue:* = Number((haveXField)? dataItem[_xField]:dataItem);
				var yValue:* = (haveYField)? dataItem[_yField]:i;
				
				var stackedValue:Object = stackedYValueDictionary[yValue];
				if(stackedValue == null)
				{
					stackedValue = 0;
				}
				xValue += stackedValue;
				chartItem.minValue = stackedValue;
				stackedYValueDictionary[yValue] = xValue;
				chartItem.yValue = yValue;
				chartItem.xValue = xValue;
				maxValue = Math.max(maxValue,xValue);
				
				i++;
				cursor.moveNext();				
			}			
		}
		
		_stacked = (previousElement != null);
		invalidateMapping(true);
		invalidateData(false);
		return maxValue;
	}

	/**
	 *  @inheritDoc
	 */
	override protected function invalidateData(invalid:Boolean=true):void
	{
		if(_stacker != null)
			_stacker.invalidateStacking();
		super.invalidateData(invalid);		
	}

	/**
	 *  @inheritDoc
	 */
	override protected function invalidateMapping(invalid:Boolean=true):void
	{
		if(_stacker != null)
			_stacker.invalidateStacking();
		super.invalidateMapping(invalid);		
	}

	/**
	 *  The stack totals for the series.
	 *
	 *  @param totals The totals to set.
	 */
	public function set stackTotals(totals:Dictionary):void
	{
		if (totals != null)
		{
			var cache:Array = _renderData.cache;
			var n:int = _renderData.cache.length;
			for (var i:int = 0; i < n; i++)
			{
				var item:BarSeriesItem = cache[i];
				var total:Number = totals[item.yValue];
				item.xValue = Math.min(100, Number(item.xValue) / total * 100);
				item.minValue =
					Math.min(100, Number(item.minValue) / total * 100);
			}
		}
	}

	/**
	 *  @private
	 */
	override protected function updateData():void
	{
		if(_stacker != null)
		{
			_stacker.stack();
			super.updateData();
			return;
		}	
		
		_stacked = false;
		
		var renderDataType:Class = this.renderDataType;
		_renderData= new renderDataType();
		
		_renderData.cache = [];

		var i:int = 0;
		var itemClass:Class = itemType;
		if (cursor != null)
		{
			cursor.seek(CursorBookmark.FIRST);
			while (!cursor.afterLast)
			{
				_renderData.cache[i] =
					new itemClass(this,cursor.current, i);
				i++;
				cursor.moveNext();
			}
			
		}

		cacheDefaultValues(_xField, _renderData.cache, "xValue");
			
		cacheIndexValues(_yField, _renderData.cache, "yValue");

		if (_minField != "")		
		{
			cacheNamedValues(_minField, _renderData.cache, "minValue");
			_renderData.renderedBase = NaN;
		}

		super.updateData();
	}

	/**
	 *  @private
	 */
	override protected function updateMapping():void
	{
		dataTransform.getAxis(CartesianTransform.HORIZONTAL_AXIS).mapCache(
			_renderData.cache, "xValue", "xNumber");
		
		dataTransform.getAxis(CartesianTransform.VERTICAL_AXIS).mapCache(
			_renderData.cache, "yValue", "yNumber", (_yField == ""));
		
		if (_minField != "" || _stacked)
		{
			dataTransform.getAxis(CartesianTransform.HORIZONTAL_AXIS).mapCache(
				_renderData.cache, "minValue", "minNumber");
		}

		// now convert
		if (_yField != "")
			_renderData.cache.sortOn("yNumber", Array.NUMERIC);		
		
		super.updateMapping();
	}

	/**
	 *  @private
	 */
	override protected function updateFilter():void
	{
		if (filterData)
		{
			_renderData.filteredCache = _renderData.cache.concat();
			
			dataTransform.getAxis(CartesianTransform.HORIZONTAL_AXIS).filterCache(
				_renderData.filteredCache, "xNumber", "xFilter");
			stripNaNs(_renderData.filteredCache, "xFilter");
			
			dataTransform.getAxis(CartesianTransform.VERTICAL_AXIS).filterCache(
				_renderData.filteredCache, "yNumber", "yFilter");
			stripNaNs(_renderData.filteredCache, "yFilter");
			
			if (_minField != "" || _stacked)		
			{
				dataTransform.getAxis(CartesianTransform.HORIZONTAL_AXIS).filterCache(
					_renderData.filteredCache, "minNumber", "minFilter");
				stripNaNs(_renderData.filteredCache, "minFilter");
			}
		}
		else
		{
			_renderData.filteredCache = _renderData.cache;
		}

		super.updateFilter();
	}
	
	/**
	 *  @private
	 */
	override protected function updateTransform():void
	{
		if (_minField != "" || _stacked)		
		{	
			dataTransform.transformCache(
				_renderData.filteredCache, "minNumber", "min", "", "");
		}
		else
		{
			var baseVal:Number = dataTransform.getAxis(CartesianTransform.HORIZONTAL_AXIS).baseline;
			var stub:Array = [ { xNumber: baseVal } ];
			dataTransform.transformCache(stub, "xNumber", "x", null, null);
			_renderData.renderedBase = stub[0].x;
			var len:int = _renderData.filteredCache.length;
			for (var i:int = 0; i < len; i++)
			{
				_renderData.filteredCache[i].min = _renderData.renderedBase;
			}
		}

		dataTransform.transformCache(
			_renderData.filteredCache, "xNumber", "x", "yNumber", "y");

		var unitSize:Number = dataTransform.getAxis(CartesianTransform.VERTICAL_AXIS).unitSize;
		var params:Array =
		[
			{ yNumber: 0 },
			{ yNumber: _barWidthRatio*unitSize/2 },
			{ yNumber: _offset*unitSize }
		];
		
		dataTransform.transformCache(params, null, null, "yNumber", "y");

		_renderData.renderedHalfWidth = params[0].y -  params[1].y;
		
		if (_offset == 0)
			_renderData.renderedYOffset = 0;
		else
			_renderData.renderedYOffset = params[2].y - params[0].y;

		if (_maxBarWidth && _maxBarWidth < _renderData.renderedHalfWidth)
		{
			_renderData.renderedYOffset *= _maxBarWidth / _renderData.renderedHalfWidth;
			_renderData.renderedHalfWidth = _maxBarWidth;
		}

		super.updateTransform();
	}

	/**
	 *  @private
	 */
	override public function getElementBounds(renderData:Object):void
	{
		var cache :Array = renderData.cache;
		var rb :Array = [];
		var sampleCount:int = cache.length;		

		var maxBounds:Rectangle

		if (sampleCount == 0)
		{
			maxBounds= new Rectangle(0,0,unscaledWidth,unscaledHeight);
		}
		else
		{
			var bo:Number = renderData.renderedHalfWidth +
							renderData.renderedYOffset;
			var to:Number = -renderData.renderedHalfWidth +
							renderData.renderedYOffset;

			var v:Object = cache[0];
			maxBounds= new Rectangle(v.x, v.y, 0, 0);
			for (var i:int = 0; i < sampleCount; i++)
			{
				v = cache[i];
				var l:Number = Math.min(v.min, v.x);
				var b:Rectangle = new Rectangle(l, v.y + to,
												Math.max(v.min, v.x) - l,
												bo - to);
				
				maxBounds.left = Math.min(maxBounds.left, b.left);
				maxBounds.top = Math.min(maxBounds.top, b.top);
				maxBounds.right = Math.max(maxBounds.right, b.right);
				maxBounds.bottom = Math.max(maxBounds.bottom, b.bottom);
				
				rb[i] = b;
			}
		}
		
		renderData.elementBounds = rb;
		renderData.bounds =  maxBounds;
	}

	/**
	 *  @private
	 */
	override public function beginInterpolation(sourceRenderData:Object,
												destRenderData:Object):Object
	{
		var idata:Object = initializeInterpolationData(
			sourceRenderData.cache, destRenderData.cache,
			{ x: true, y: true, min: true }, itemType,
			{ sourceRenderData: sourceRenderData,
			  destRenderData: destRenderData });
		
		var interpolationRenderData:BarSeriesRenderData =
			BarSeriesRenderData(destRenderData.clone());

		interpolationRenderData.cache = idata.cache;	
		interpolationRenderData.filteredCache = idata.cache;	

		transitionRenderData = interpolationRenderData;
		
		return idata;
	}

	/**
	 *  @private
	 */
	override protected function getMissingInterpolationValues(
									sourceProps:Object, srcCache:Array,
									destProps:Object, destCache:Array,
									index:Number, customData:Object):void
	{
		for (var propName:String in sourceProps)
		{
			var src:Number = sourceProps[propName];
			var dst:Number = destProps[propName];

			if (propName == "x" || propName == "min")
			{
				if (isNaN(src))
					src = customData.destRenderData.renderedBase;
				
				if (isNaN(dst))
					dst = customData.sourceRenderData.renderedBase;
			}
			else if (propName == "y")
			{
				if (isNaN(src))
					src = dst;
				
				if (isNaN(dst))
					dst = src;
			}

			sourceProps[propName] = src;
			destProps[propName] = dst;
		}		
	}
	
	//--------------------------------------------------------------------------
	//
	//  Methods
	//
	//--------------------------------------------------------------------------

	/**
	 *  Customizes the item renderer instances that are used to represent the chart. This method is called automatically
	 *	whenever a new item renderer is needed while the chart is being rendered. You can override this method to add your own customization as necessary.
	 *	@param	instance	The new item renderer instance that is being created.
	 *	@param	cache		The InstanceCache that is being used to manage the item renderer instances.
	 */
	protected function applyItemRendererProperties(instance:DisplayObject,
												   cache:InstanceCache):void
	{
		if (instance is ISimpleStyleClient)
			ISimpleStyleClient(instance).styleName = this;
	}

	/**
	 *  @private
	 */
	private function formatDataTip(hd:HitData):String
	{
		var dt:String = "";
		
		var n:String = displayName;
		if (n && n != "")
			dt += "<b>" + n + "</b><BR/>";
		
		var yName:String = dataTransform.getAxis(CartesianTransform.VERTICAL_AXIS).displayName;
		if (yName != "")
			dt += "<i>" + yName + ":</i> ";
		dt += dataTransform.getAxis(CartesianTransform.VERTICAL_AXIS).formatForScreen(
			BarSeriesItem(hd.chartItem).yValue) + "\n";

		var xName:String = dataTransform.getAxis(CartesianTransform.HORIZONTAL_AXIS).displayName;
		if (minField == "")
		{
			if (xName != "")
				dt += "<i>" + xName + ":</i> ";
			dt += dataTransform.getAxis(CartesianTransform.HORIZONTAL_AXIS).formatForScreen(
				BarSeriesItem(hd.chartItem).xValue) + "\n";
		}
		else
		{
			if (xName != "")
				dt += "<i>" + xName + " (high):</i> ";
			else
				dt += "<i>high:</i> ";
			dt += dataTransform.getAxis(CartesianTransform.HORIZONTAL_AXIS).formatForScreen(
				BarSeriesItem(hd.chartItem).xValue) + "\n";

			if (xName != "")
				dt += "<i>" + xName + " (low):</i> ";
			else
				dt += "<i>low:</i> ";
			dt += dataTransform.getAxis(CartesianTransform.HORIZONTAL_AXIS).formatForScreen(
				BarSeriesItem(hd.chartItem).minValue) + "\n";
		}
		
		return dt;
	}
}

}
